package com.derbysoft.lowcostadapter.schedule.parse.impl;

import com.derbysoft.lowcostadapter.commons.model.Hotel;
import com.derbysoft.lowcostadapter.commons.model.RoomOccupancy;
import com.derbysoft.lowcostadapter.commons.model.Status;
import com.derbysoft.lowcostadapter.commons.redis.service.OccupancyCacheService;
import com.derbysoft.lowcostadapter.commons.repository.HotelRepository;
import com.derbysoft.lowcostadapter.commons.utils.DateTimeUtils;
import com.derbysoft.lowcostadapter.schedule.parse.ParseRoomOccupancyService;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.LineIterator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

@Component("parseRoomOccupancyService")
public class ParseRoomOccupancyServiceImpl implements ParseRoomOccupancyService {
    private final AtomicBoolean updateRoomOccupancy = new AtomicBoolean(false);
    private static final int PRINT_STEP = 1000;
    private static final int batchImportSize = 100000;

    private Logger logger = LoggerFactory.getLogger(ParseRoomOccupancyServiceImpl.class);

    @Autowired
    private HotelRepository hotelRepository;

    @Autowired
    private OccupancyCacheService occupancyCacheService;

    @Override
    public synchronized boolean importRoomOccupancy(InputStream inputStream) {
        try {
            updateRoomOccupancy.set(true);
            logger.info("importRoomOccupancy begin");
            executeImportRestrictions(inputStream);
            logger.info("importRoomOccupancy end");
            return true;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        } finally {
            updateRoomOccupancy.set(false);
        }
        return false;
    }

    private void executeImportRestrictions(InputStream inputStream) throws IOException {
        Map<Integer, Hotel> hotelMap = hotelRepository.loadHotelMap(Status.ENABLED);

        List<RoomOccupancy> roomOccupancies = new ArrayList<RoomOccupancy>();

        long totalBytes = inputStream.available();
        LineIterator lineIterator = IOUtils.lineIterator(inputStream, "UTF-16");
        long line = 0;

        while (lineIterator.hasNext()) {
            String restrictionInfo = lineIterator.nextLine();
            line++;
            String[] split = restrictionInfo.split(",");

            RoomOccupancy roomOccupancy = convertRoomOccupancy(split, hotelMap);
            if (roomOccupancy != null) {
                roomOccupancies.add(roomOccupancy);
                if (roomOccupancies.size() > batchImportSize) {
                    occupancyCacheService.saveAll(roomOccupancies);
                    roomOccupancies.clear();
                }
            }

            if (line % PRINT_STEP == 0) {
                printProcess("importRoomOccupancy", totalBytes, inputStream, line);
            }
        }
        occupancyCacheService.saveAll(roomOccupancies);
    }

    private RoomOccupancy convertRoomOccupancy(String[] infos, Map<Integer, Hotel> hotelMap) {
        try {
            Hotel hotel = hotelMap.get(getIntVal(infos, Field.PROPERTY_ID));
            if (hotel == null) {
                return null;
            }
            RoomOccupancy roomOccupancy = new RoomOccupancy();
            roomOccupancy.setPropertyID(String.valueOf(hotel.getPropertyID()));
            roomOccupancy.setPropertyRoomTypeID(getIntVal(infos, Field.PROPERTY_ROOM_TYPE_ID));
            roomOccupancy.setStart(getDateStringVal(infos, Field.START_DATE));
            roomOccupancy.setEnd(getDateStringVal(infos, Field.END_DATE));
            roomOccupancy.setMinOccupancy(getIntVal(infos, Field.MIN_OCCUPANCY));
            roomOccupancy.setMaxOccupancy(getIntVal(infos, Field.MAX_OCCUPANCY));
            roomOccupancy.setMinAdults(getIntVal(infos, Field.MIN_ADULTS));
            roomOccupancy.setMaxAdults(getIntVal(infos, Field.MAX_ADULTS));
            roomOccupancy.setMaxAdultsWithChildren(getIntVal(infos, Field.MAX_ADULTS_WITH_CHILDREN));
            roomOccupancy.setMaxChildren(getIntVal(infos, Field.MAX_CHILDREN));
            return roomOccupancy;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    private void printProcess(String title, long totalBytes, InputStream inputStream, long line) throws IOException {
        long available = inputStream.available();
        long processed = totalBytes - available;
        double percent = 100.0 * processed / totalBytes;
        String message = String.format("%s Line:(%d) Processed:(%d/%d) Precent:(%.2f%%)",
                title, line, processed, totalBytes, percent);
        logger.info(message);
    }

    public void setHotelRepository(HotelRepository hotelRepository) {
        this.hotelRepository = hotelRepository;
    }

    private String getStringVal(String[] infos, Field field) {
        return infos[field.ordinal()];
    }

    private int getIntVal(String[] infos, Field field) {
        return Integer.valueOf(getStringVal(infos, field));
    }

    private String getDateStringVal(String[] infos, Field field) {
        return DateTimeUtils.formatDate(DateTimeUtils.parse(getStringVal(infos, field)));
    }

    private enum Field {
        PROPERTY_ID,
        PROPERTY_NAME,
        PROPERTY_ROOM_TYPE_ID,
        START_DATE,
        END_DATE,
        ROOM_TYPE_CODE,
        ROOM_TYPE_DESCRIPTION,
        MIN_OCCUPANCY,
        MAX_OCCUPANCY,
        STD_OCCUPANCY,
        MIN_ADULTS,
        MAX_ADULTS,
        MAX_ADULTS_WITH_CHILDREN,
        MAX_CHILDREN,
        INFANTS,
        INFANTS_INCLUDED_OCCUPANCY,
        CHILD_AGE_FROM,
        CHILD_AGE_TO
    }

    public void setOccupancyCacheService(OccupancyCacheService occupancyCacheService) {
        this.occupancyCacheService = occupancyCacheService;
    }

    @Override
    public boolean isParsing() {
        return updateRoomOccupancy.get();
    }
}
